package org.yun.redpack;


import com.google.common.util.concurrent.RateLimiter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.yun.biz.dao.RedPackRepository;
import org.yun.biz.model.RedPack;

import org.yun.constants.RedisConstant;
import org.yun.exception.BizException;
import org.yun.exception.ErrorCode;
import org.yun.util.*;

import javax.annotation.Resource;
import java.util.*;

import static org.yun.constants.Constant.*;
import static org.yun.constants.RedisConstant.RED_PACK;
import static org.yun.util.CommonUtil.*;
import static org.yun.util.RedisUtil.CAS_GRAB_RED_SCRIPT;

/**
 * @ProjectName: no-concurrent
 * @ClassName: TimelyImpl
 * @Description: cas策略
 * @Author: liyunfeng31
 * @Date: 2020/10/4 23:22
 */
@SuppressWarnings("UnstableApiUsage")
@Slf4j
@Component
public class TimelyImpl implements RedPackService {


    @Resource
    private RedisUtil redisUtil;

    @Resource
    private RedPackRepository redPackDao;

    final RateLimiter limiter = RateLimiter.create(200.0);


    @Transactional(rollbackFor = Exception.class)
    @Override
    public Long createRedPack(RedPack redPack, Long userId) {
        // 参数校验
        checkRedPackParam(redPack);
        // 根据支付凭证校验是否合法
        redPackDao.save(redPack);
        Map<String, String> map = redToMap(redPack);
        redisUtil.hmset(RED_PACK + redPack.getRedPackId(),map,3600*72);
        // todo 群通知
        return redPack.getRedPackId();
    }

    @Override
    public boolean clickRedPack(Long redPackId, Long userId) {
        if(!limiter.tryAcquire()){ return false; }
        Object num = redisUtil.hget(RED_PACK + redPackId, "remainingNum");
        return Integer.parseInt(num.toString()) > 0;
    }


    @Override
    public Integer grabRedPack(Long redPackId, Long userId) {
        int time = 0;
        Integer redPackAmount;
        do {
            time++;
            redPackAmount = this.tryGrab(redPackId, userId);
        } while (redPackAmount == 0 && time < 3);
        return redPackAmount;
    }



    private Integer tryGrab(Long redPackId, Long userId){
        Map<Object, Object> cache = redisUtil.hmget(RED_PACK + redPackId);
        int rNum = intVal(cache, "remainingNum");
        if(rNum == 0){
            throw new BizException(ErrorCode.RED_PACK.NO_STOCK);
        }
        int rAmount = intVal(cache, "remainingAmount");
        Integer red = RandomRedUtil.randomRed(rNum, rAmount);
        rAmount = rAmount - red;  rNum--;

        Long result = redisUtil.executeScript(
                CAS_GRAB_RED_SCRIPT,
                keys(redPackId),
                userId.toString(), param(redPackId, rAmount, rNum, red));
        if(result.equals(SUCCESS)){
            return red;
        }
        return checkResult(result);
    }


    /**
     * 获取int值
     * @param map map
     * @param key key
     * @return val
     */
    private int intVal(Map<Object, Object> map, String key){
        Object rNumObj = map.get(key);
       return Integer.parseInt(rNumObj.toString());
    }


    /**
     * 检查结果 无库存-已抢过抛异常
     * @param result 0是版本号比对失败 用于CAS
     * @return code
     */
    private int checkResult(Long result){
        if(result.equals(PARTICIPATED)){
            throw new BizException(ErrorCode.RED_PACK.PARTICIPATED);
        }
        if(result.equals(NO_STOCK_)){
            throw new BizException(ErrorCode.RED_PACK.NO_STOCK);
        }
        return 0;
    }
}
